<?php
/**
 * Contains mpPackager related classes.
 *
 * @category    Development
 * @package 	mpPackager
 * @author		Murat Purc <murat@purc.de>
 * @copyright   Copyright (c) 2009-2010 Murat Purc (http://www.purc.de)
 * @license     http://www.gnu.org/licenses/gpl-2.0.html - GNU General Public License, version 2
 * @version     $Id: mpPackager.php 5 2010-06-04 16:57:15Z purcaholic $
 */


/**
 * mpPackagerException class
 *
 * @category    Development
 * @package 	mpPackager
 * @author		Murat Purc <murat@purc.de>
 */
class mpPackagerException extends Exception {}


/**
 * Deployment class which provides simple features like filecopy, dircopy and tagreplacement in
 * copied files.
 *
 * @category    Development
 * @package 	mpPackager
 * @author		Murat Purc <murat@purc.de>
 */
class mpPackager
{

    /**
     * Source base directory
     * @var  string
     */
    private $_source = '';

    /**
     * Destination base directory
     * @var  string
     */
    private $_dest = '';

    /**
     * Folderperms (folder mode)
     * @var  int
     */
     private $_folderPerms = 0755;

    /**
     * Fileperms (file mode)
     * @var  int
     */
     private $_filePerms = 0644;

    /**
     * List file extensions where the content ist to analyze for existing replacement tags
     * @var  array
     */
    private $_replaceExts = array();

    /**
     * List replacement tags which should replaced in copied files and match defined extension
     * (see mpPackager#$_replaceExts above)
     * @var  array
     */
    private $_replacements = array();

    /**
     * List of ignore dirs (e. g. '.', '..', '.svn')
     *
     * @var  array
     */
    private $_ignoreDirs = array('.', '..');


    /**
     * List of files to ignore (e. g. 'temp.txt', 'log.txt')
     *
     * @var  array
     */
    private $_ignoreFiles = array();


    /**
     * Constructor.
     *
     * @param   array  $options
     * @return  void
     */
    public function __construct(array $options=array())
    {
        if (count($options) > 0) {
            $this->setOptions($options);
        }
    }


    /**
     * Packager options setter.
     *
     * @param   array  $options  Assoziative options array as follows:
     *                           - $options['replaceExts'] = File extensions of file to parse for replacements
     *                           - $options['replacements'] = Assoziative array of replacements
     *                           - $options['ignoreDirs'] = List of directories to ignore during copy
     *                           - $options['ignoreFiles'] = List of files to ignore during copy
     *                           - $options['folderPerms'] = Default folder permission
     *                           - $options['filePerms'] = Default file permission
     * @return  void
     */
    public function setOptions(array $options)
    {
        if (isset($options['replaceExts']) && is_array($options['replaceExts'])) {
            $this->_replaceExts = $options['replaceExts'];
        }
        if (isset($options['replacements']) && is_array($options['replacements'])) {
            $this->_replacements = $options['replacements'];
        }
        if (isset($options['ignoreDirs']) && is_array($options['ignoreDirs'])) {
            $this->_ignoreDirs = array_merge($this->_ignoreDirs, $options['ignoreDirs']);
        }
        if (isset($options['ignoreFiles']) && is_array($options['ignoreFiles'])) {
            $this->_ignoreFiles = array_merge($this->_ignoreFiles, $options['ignoreFiles']);
        }
        if (isset($options['folderPerms']) && preg_match('/[0-7]{3,4}/', $options['folderPerms'])) {
            $this->_folderPerms = $options['folderPerms'];
        }
        if (isset($options['filePerms']) && preg_match('/[0-7]{3,4}/', $options['filePerms'])) {
            $this->filePerms = $options['_filePerms'];
        }
    }


    /**
     * Set the source directory.
     *
     * @param   string  $source
     * @return  void
     */
    public function setSource($source)
    {
        if (!is_dir($source)) {
            throw new mpPackagerException('setSource(): Invalid source directory');
        }
        $this->_source = $source;
    }


    /**
     * Set the destination directory.
     *
     * @param   string  $dest
     * @return  void
     */
    public function setDestination($dest)
    {
        $this->_dest = $dest;
    }


    /**
     * Creates the directory, does it also recursively
     *
     * @param   string  $dir
     * @param   int     $mode
     * @param   bool    $recursive
     * @return  bool
     */
    public function makeDestDir($dir='', $mode=0777, $recursive=true)
    {
        $dir      = str_replace($this->_dest, '', $dir);
        $pathName = $this->_dest . $dir;
        return $this->_makeDir($pathName, $mode, (bool) $recursive);
    }


    /**
     * Copies file from source to destination.
     *
     * @param  string  $file      File name and path to copy (starting from source directory)
     * @param  string  $destFile  Destination file name and path (starting from destination directory)
     */
    public function copyFile($file, $destFile='')
    {
        if ($destFile == '') {
            $destFile = $file;
        }

        $dir = dirname($file);
        if (!is_dir($this->_dest . $dir)) {
            $this->makeDestDir($dir);
        }

        $this->_copyFile($this->_source . $file, $this->_dest . $destFile);
    }


    /**
     * Copies full directory from source to destination
     *
     * @param  string  $source  Path beginning from source dir to copy
     */
    public function copyDir($source)
    {
        if (!is_dir($this->_source . $source)) {
            return;
        }

        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($this->_source . $source),
            RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($iterator as $file) {
            $file = new mpPackager_FileInfo($file->getPathname());
            if ($this->_isToIgnore($file)) {
                continue;
            }

            $pathName = $file->getPathname();
            if ($file->isDir()) {
                $newPathName = str_replace($this->_source, $this->_dest, $pathName);
                $this->_makeDir($newPathName);
            } elseif ($file->isFile()) {
                $newPathName = str_replace($this->_source, $this->_dest, $pathName);
                $this->_copyFile($pathName, $newPathName);
            }
        }
    }


    public function deleteDestination($pathName)
    {
        $fileInfo = new mpPackager_FileInfo($this->_dest . $pathName);
        if ($fileInfo->isFile()) {
            unlink($fileInfo->getRealPath());
        } else {
            // delete tree structure recursively
            $iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($this->_dest . $pathName), 
                RecursiveIteratorIterator::CHILD_FIRST
            );
            foreach ($iterator as $fileInfo2) {
                if ($fileInfo2->isDir()) {
                    rmdir($fileInfo2->getRealPath());
                } else {
                    unlink($fileInfo2->getRealPath());
                }
            }
            rmdir($fileInfo->getRealPath());
        }
    }


    public function unlinkDestinationFile($file)
    {
        if (is_file($this->_dest . $file)) {
            @unlink($this->_dest . $file);
        }
    }


    public function removeDestinationDir($dir)
    {
        if (is_dir($this->_dest . $dir)) {
            @rmdir($this->_dest . $dir);
        }
    }


    private function _copyFile($source, $target, $mode=null)
    {
        // first of all create destination directory, if needed
        $fileInfo = new mpPackager_FileInfo($target);
        $path     = $fileInfo->getPath() . '/';
        $this->_makeDir($path);

        copy($source, $target);
        if ($mode == null) {
            $mode = $this->_filePerms;
        }
        chmod($target, $mode);
        $this->_processReplacements($target);
        $this->_printOk($target);
    }


    /**
     * Creates the directory, does it also recursively
     *
     * @param   string  $dir
     * @param   int     $mode
     * @param   bool    $recursive
     * @return  bool
     */
    public function _makeDir($dir='', $mode=0777, $recursive=true)
    {
        $recursive = (bool) $recursive;
        return is_dir($dir) || @mkdir($dir, $mode, $recursive);
    }


    private function _processReplacements($file)
    {
        $parts = pathinfo($file);
        if (!isset($parts['extension']) || !in_array($parts['extension'], $this->_replaceExts)) {
            return;
        }

        if (count($this->_replacements) > 0) {
            if (!$content = file_get_contents($file)) {
                return;
            }
            foreach ($this->_replacements as $key => $value) {
                $content = str_replace($key, $value, $content);
            }

            file_put_contents($file, $content);
        }
    }


    private function _printOk($file)
    {
        $output = $file;
        if (strlen($output) > 64) {
            $output = '...' . substr($output, strlen($output)-64, 64);
        }
        echo 'ok: ' . $output . "\n";
    }


    /**
     * Checks if file or folder is to ignore from copy process
     *
     * @param   mpPackager_FileInfo  $fileInfo
     * @return  bool
     */
    private function _isToIgnore(mpPackager_FileInfo $fileInfo)
    {
        $pathName = $fileInfo->getPathname();
        if ($fileInfo->isDir()) {
            $pathName .= '/';
        }
        
        // check for directories to ignore
        foreach ($this->_ignoreDirs as $item) {
            if (strpos($pathName, '/' . $item . '/') !== false) {
                return true;
            }
        }
        
        // check for files to ignore
        if ($fileInfo->isFile()) {
            $baseName = $fileInfo->getbaseName();
            foreach ($this->_ignoreFiles as $item) {
                if ($baseName == $item) {
                    return true;
                }
            }
        }
        return false;
    }

}
